######################################
#                                    #
#  AutoArchiveManager - By Yingest   #
#                                    #
#    The Core Class for CC Taskss    #
#                                    #
######################################

import os
import re
import time
import yaml
from pathlib import Path
import logging
import threading
import shutil
from queue import Queue

try:
    from Modules.animate import animate
except ImportError:  # 回退./main.py时平行引用 __package__ 为 None
    from ..animate import animate

from .binToPNG import convert_bin_to_bmp as b2bmp


class CCTasks:
    MARK_PATTERN = re.compile(r'CC Tasks(?:\[(.*?)\])?.mark')

    def __init__(self, path: str, relative_path: list[str],
                 logger: logging.Logger):
        self.path = path
        self.relative = relative_path
        self.logger = logger
        self.ccd = 0
        self.skd = 0
        self.all_ccd = {}
        self.done = []
        self.history = []
        self.mark_path = ''
        self.markDir = ''
        self.msg_q = Queue()
        self.ccend = threading.Event()

    def _cc_check(self, tasks, path) -> bool:
        self.done = []
        self.history = []
        self.mark_path = ''
        self.markDir = ''
        pending_tasks = []
        path_obj = Path(path)
        self.markDir = path
        for file in path_obj.iterdir():
            if file.is_file():
                matches = self.MARK_PATTERN.match(file.name)
                if matches:
                    if matches.group(1) is None:
                        self.done = []
                    else:
                        self.done = matches.group(1).split('-')
                    self.mark_path = str(file)
                    break
        for task in tasks:
            if task[0] not in self.done: pending_tasks.append(task)
        if pending_tasks and self.mark_path:
            try:
                with open(self.mark_path, 'r') as f:
                    mark = yaml.safe_load(f)
                    self.history = mark['history'] if mark else []
            except (KeyError, yaml.YAMLError) as e:
                raise (f"read Mark history with issues: {str(e)}")
        return pending_tasks

    def _update_mark_file(self) -> bool:
        if self.done:
            new_full_path = os.path.join(
                self.markDir, 'CC Tasks[' + '-'.join(self.done) + '].mark')
        else:
            new_full_path = os.path.join(self.markDir, 'CC Tasks.mark')

        if self.mark_path and Path(self.mark_path) != Path(new_full_path):
            try:
                os.rename(self.mark_path, new_full_path)
            except OSError as e:
                self.logger.error(f"Error renaming file: {e}")
                return False
        self.mark_path = new_full_path
        try:
            with open(self.mark_path, 'w') as f:
                mark = {'done': self.done, 'history': self.history}
                yaml.dump(mark, f, default_flow_style=False)
        except (KeyError, yaml.YAMLError) as e:
            self.logger.error(f"write Mark history with issues: {str(e)}")
            return False
        return True

    def _cc_action(self, path: str, task: tuple[str, str, int, list[str], str],
                   target: str) -> bool:
        """copy or clean a file or folder."""
        action = task[1]
        argv = task[4]
        try:
            if action in ['cv']:
                if not path.endswith('.bin'):
                    self.logger.warning(f"[CC] [SKIP] Not a .bin file: {path}")
                    return False
                targetPath = path.replace('.bin', argv).split('\\')[-1]
                if os.path.isfile(path):
                    b2bmp(path, argv)
                    self._record_log(f"[CC]  [Convert] {path} -> {targetPath}")
                    os.remove(path)
                    self._record_log(f"[CC]  [DEL] {path}")
                    return True
                else:
                    return False
            if action in ['co', 'cc']:
                if os.path.isfile(path):
                    if not os.path.exists(os.path.dirname(target)):
                        os.makedirs(os.path.dirname(target))
                    shutil.copyfile(path, target)
                else:
                    shutil.copytree(path, target)
                self._record_log(f"[CC]  [COPY] {path} -> {target}")
                return True
            if action in ['cl', 'cc']:
                if os.path.isfile(path): os.remove(path)
                else: shutil.rmtree(path)
                self._record_log(f"[CC]  [DEL] {path}")
                return True
        except Exception as e:
            self.logger.error(f"Error occur during {action}ing {path}: {e}")
            return False

    def _cc_lookup(
            self,
            tasks: list[tuple[str, str, int, list[str], str]],
            mark_level: int,
            re_ph: list[str] = None) -> tuple[int, int, dict[str, bool]]:
        """Clean the folder based on specified conditions."""
        if re_ph is None: re_ph = []
        current_time = int(time.time())
        # Create the current Folder Path
        ab_ph = os.path.join(self.path, *re_ph)
        if mark_level == 0:
            tasks = self._cc_check(tasks, ab_ph)
            for task in tasks:
                self.all_ccd[task[0]] = True  # Init for all tasks clean status
        if not tasks:
            return 0, 0, {}

        for file_name in os.listdir(ab_ph):
            file_abph = os.path.join(ab_ph, file_name)
            updt_tasks = []
            for task in tasks:
                name, action, timing, match_str, argv = task
                match_level = len(match_str)
                if re.search(match_str[0], file_name):
                    if match_level == 1:
                        file_time = int(os.stat(file_abph).st_mtime)
                        if current_time - file_time < timing:
                            self.all_ccd[name] = False
                            self.skd += 1
                            self._msg_put("SK", file_name)
                            continue
                        target_path = os.path.join(argv, *self.relative,
                                                   *re_ph, file_name)
                        if self._cc_action(file_abph, task, target_path):
                            self.ccd += 1
                            timestamp = time.strftime('[%Y-%m-%d %H:%M:%S]  ',
                                                      time.localtime())
                            self.history.append(
                                f'{timestamp}[{name}]  [{action}]  {file_name}'
                            )
                            self._msg_put(action, file_name)
                        else:
                            self.skd += 1
                            self.all_ccd[name] = False
                            self._msg_put("X", file_name)
                    else:
                        updt_tasks.append(
                            (name, action, timing, match_str[1:], argv))
            if os.path.isdir(file_abph) and updt_tasks:
                self._cc_lookup(updt_tasks, mark_level - 1,
                                [*re_ph, file_name])

        if mark_level == 0:
            try:
                for key in self.all_ccd:
                    if self.all_ccd[key] and key not in self.done:
                        self.done.append(key)
                if not self._update_mark_file():
                    for key in self.all_ccd:
                        self.all_ccd[key] = False
            except Exception as e:
                self.logger.error(f"Error write the mark file: {e}")
                for key in self.all_ccd:
                    self.all_ccd[key] = False
        return

    def _record_log(self, msg: str):
        self.logger.info(msg, extra={"file_only": True})

    def _msg_put(self, act: str, msg: str) -> None:
        while not self.msg_q.empty():
            self.msg_q.get()
        if len(msg) > 25: msg = ".." + msg[-25:]
        self.msg_q.put((act, msg))

    def _cc_main(self, tasks: list[tuple[str, str, int, list[str], str]],
                 mark_level: int) -> None:
        self._cc_lookup(tasks, mark_level)
        while not self.msg_q.empty():
            time.sleep(0.1)
        self.msg_q.put(("END", ""))
        self.ccend.set()
        return

    def run(
        self,
        tasks: list[tuple[str, str, int, list[str], str]],
        mark_level: int,
    ) -> tuple[int, int, bool]:
        """Run the CC task in another Thread."""
        cc_thread = threading.Thread(target=self._cc_main,
                                     args=(tasks, mark_level))
        cc_thread.daemon = True
        ani = animate(25, 12)
        action, msg, flash, disp = "", "", "", ""

        cc_thread.start()
        while True:
            time.sleep(0.15)
            if not self.msg_q.empty():
                action, msg = self.msg_q.get()
                if action == "END": print(flash, end='')
                else:
                    disp = f"{ani.next2()}| {self.ccd}|{self.skd} {ani.timetake()} [{action}] {msg}"
                    print(flash + disp, end='')
                    flash = "\r" + len(disp) * " " + "\r"
            else:
                disp = f"{ani.next2()}| {self.ccd}|{self.skd} {ani.timetake()} [{action}] {msg}"
                print(flash + disp, end='')
                flash = "\r" + len(disp) * " " + "\r"

            if self.ccend.is_set():
                break

        cc_thread.join()
        return
